[TensorFlow Certification Day14] TensorFlow Embedded文本相關model


Posted by Kled on 2020-09-17

接續著上一篇的, 繼續努力
把這邊再練習熟悉就可以去考試了

先再複習上次的imdb

#load tfds data
imdb, info = tfds.load("imdb_reviews", with_info=True, as_supervised=True)
train_data, test_data = imdb['train'], imdb['test']

training_sentences = []
training_labels = []

testing_sentences = []
testing_labels = []

#s,l都是儲存成tf.Tensor
#透過.numpy()轉換成numpy
for s,l in train_data:
    training_sentences.append(str(s.numpy()))
    training_labels.append(l.numpy())

for s, l in test_data:
    testing_sentences.append(str(s.numpy()))
    testing_labels.append(l.numpy())

training_labels_final = np.array(training_labels)
testing_labels_final = np.array(testing_labels)

要把文字送進去model訓練有兩個問題

  1. 要把文字數字化
  2. input要相同長度

接下來就是今天的重頭戲, 首先是Tokenizer

sentences = [
    'I love my dog',
    'I love my cat'
]
#將文字變成數字, token化
#加標點符號不影響, 不會因為dog!, 就增加一個index
#透過空格或, 分割語句
#不分大小寫 I = i
#tokenizer會依照出現的頻率來排序, 越前面的出現頻率越高
tokenizer = Tokenizer(num_words = 100)
#根據fit_on_texts自動去tokenize, 會自動定義出token需要的長度有多少
tokenizer.fit_on_texts(sentences)
#word_index 是詞對照到數字的dict
word_index = tokenizer.word_index
print(word_index)
#{'i': 1, 'my' : 3, 'dog' : 4, 'cat' : 5, 'love' : 2}

#建立好token後, 對想轉換的句子做texts_to_sequences
sentences = [
    'I love my dog',
    'I love my cat'
    'You love my dog!',
    'Do you think my dog is amazing?'
]

sequences = tokenizer.texts_to_sequences(sentences)
print(sequences)
#沒看過的詞不會出現
#[[4, 2, 1, 3], [4, 2, 1, 6], [5, 2, 1, 3], [7, 5, 8, 1, 3, 9, 10]]

上面看到遇到沒看過的字就麻煩了, 有兩個方法可以解決這個問題

  1. database夠大
  2. 給予不知道的詞一個特殊的值

另外透過pad_sequences補充到相同長度

#要特別注意<OOV>不能跟真實數據的詞一樣, 否則會混淆
#這邊會自動把沒看過的東西補<OOV>, 然後再轉成數字
tokenizer = Tokenizer(num_words = 100, oov_token="<OOV>")
from tensorflow.keras.preprocessing.sequence import pad_sequences
#會把每一段padding補0到相同長度, 會自動找語句最長的當maxlen
padded = pad_sequences(sequences)
#限制最長的語句長度, post是指把pad補0補在後面, 預設是'pre'
padded = pad_sequences(sequences, padding='post', maxlen=5)
#建立model
#vocab_size是word_index size
#embedding_dim是希望詞向量的維度
#max_length是想要把多少個詞切成一個input
model = tf.keras.Sequential([
    tf.keras.layers.Embedding(vocab_size, embedding_dim, input_length=max_length),
    tf.keras.layers.Flatten(), #或使用tf.keras.layers.GlobalAveragePooling1D(),
    #Global出來的Cell數會比較少, 所以會比較快
    tf.keras.layers.Dense(6, activation='relu'),
    tf.keras.layers.Dense(1, activation='sigmoid')
])

model.fit(padded,
           training_labels_final,
           epochs = num_epochs,
           validation_data = (testing_padded, testing_labels_final))


e = model.layers[0]
weights = e.get_weights()[0]
print(weights.shape) #10000*16
#原本的word_index的key是word
#現在想把key改成數字 value為word
reverse_word_index = dict([(value, key) for (key, value) in word_index.items()])

import io
out_v = io.open('vecs.tsv', 'w', encoding='utf-8')
out_m = io.open('meta.tsv', 'w', encoding='utf-8')

#weights等於是words在這些項量上的投影

for word_num in range(1, vocab_size):
    word = reverse_word_index[word_num]
    embeddings = weights[word_num]
    out_m.write(word + "\n")
    out_v.write('\t'.join([str(x) for x in embeddings]) + "\n")

out_v.close()
out_m.close()

#https://projector.tensorflow.org/  可以看project後的分類效果

改成用subword, subword介於詞與字符之間, 能夠比較好的平衡OOV問題
subword有點像我們看英文的字首字尾

  1. 準備足夠大的訓練database
  2. 確定期望的subword詞表大小
  3. 將單詞拆分為字符序並在結尾添加"</w>", 統計單詞頻率
  4. 統計每一個連續字節對的出現頻率, 選擇最高頻率者合併成新的subword
  5. 重複第四步, 直到達到第二步設定的subword詞表大小, 或下一個最高頻率的字節對出現頻率為1

因為feature很多, 很難flatten(), 建議是使用GlobalAveragePooling1D
但是subword通常沒有意義, 所以訓練出來也會GG
需要透過RNN, 有Sequence的組起來這些subword才有意義
看起來不用會也沒有關係~~

把重點回到token來看, 這邊要做的是預測下一個字是甚麼, 自動產生文本

tokenizer = Tokenizer()
data = 'In the town of Athy one Jeremy Lanigan \n Battered away ... ...'
#corpus 這邊是把字體都變小寫, 並且每一句拆開
corpus = data.lower().split("\n")

tokenizer.fit_on_texts(corpus)
total_words = len(tokenizer.word_index) + 1 #+1個不在訓練的詞彙

input_sequences = []
#這邊做的動作是把每一句拆成從不同長度
#例如把一整句[4, 2, 66, 8, 67, 68, 69, 70]
#變成 注意下面至少是len >= 2
#[4, 2]
#[4, 2, 66]
#[4, 2, 66, 8]
#...
#[4, 2, 66, 8, 67, 68, 69, 70]
for line in corpus:
    token_list = tokenizer.texts_to_sequences([line])[0]
    for i in range(1, len(token_list)):
        n_gram_sequence = token_list[:i+1]
        input_sequences.append(n_gram_sequence)

#找出最長的sub line長度
max_sequence_len = max([len(x) for x in input_sequences]) 
#把前面都補0, 並轉換成nparray
input_sequences = np.array(pad_sequences(input_sequences, maxlen=max_sequence_len, padding='pre'))

#把每一句subline的最後一個拿去當label, 前面的全部都拿去當input data
xs = input_sequences[:,:-1]
labels = input_sequences[:,-1]
#把label換成one hot encode
ys = tf.keras.utils.to_categorical(labels, num_classes=total_words)

#Bidirectional是雙向, 可以前往後, 也可以前往後互相影響
model = Sequential()
model.add(Embedding(total_words, 64, input_length=max_sequence_len-1))
model.add(Bidirectional(LSTM(20)))
model.add(Dense(total_words, activation='softmax'))
model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy'])
history = model.fit(xs, ys, epochs=500, verbose=1)

#想要從這句話產生100個詞
seed_text = 'Lauraence went to dublin'
next_words = 100
for _ in range(next_words):
    token_list = tokenizer.texts_to_sequences([seed_text])[0]
    token_list = pad_sequences([token_list], maxlen=max_sequence_len-1, padding='pre')
    predicted = model.predict_classes(token_list, verbose=0)

    output_word = ''
    #下面是把predicted轉換成詞
    #看哪個打中了index跟predicted一樣, 這個詞就是predict的詞
    for word, index in tokenizer.word_index.items():
        if index == predicted:
            output_word = word
            break
    seed_text += ' ' + output_word
print(seed_text)

這篇先到這邊, 原本想一篇做個結尾的, 但內容有點太多,
後續還有time series model留到下一篇講解


#AI #人工智慧 #機器學習 #machine learning #TensorFlow #tensorFlow Certification #Deep Learning #深度學習







Related Posts

Android Status bar 深色模式設定

Android Status bar 深色模式設定

【Day00】前言

【Day00】前言

1731. The Number of Employees Which Report to Each Employee

1731. The Number of Employees Which Report to Each Employee


Comments